/**
 * 数字正则
 */
export const NumberRegExp = /^-?(\d|[1-9]\d+|\d\.\d+|[1-9]\d+\.\d+)$/

/**
 * 整数正则
 */
export const IntegerNumberRegExp = /^-?[1-9]\d*$/

/**
 * 浮点数正则
 */
export const FloatNumberRegExp = /^-?(\d\.\d+|[1-9]\d*\.\d+)$/

interface NumberToolOptions {
  /** 最小值限制 */
  min?: number
  /** 最大值限制 */
  max?: number
  /**
   * 是否进行安全整数校验
   * @default true
   * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger
   */
  safe?: boolean
}

export interface FloatNumberToolOptions extends NumberToolOptions {
  /** 校验/格式化的目标为浮点数 */
  mode: 'float'
  /** 精度 */
  precision?: number
}

export interface IntegerNumberToolOptions extends NumberToolOptions {
  /** 校验/格式化的目标为整数 */
  mode: 'integer'
}

/**
 * 数字字符串的校验结果
 */
export enum FLOAT_NUMBER_VALIDATOR {
  /** 有效的 */
  VALID,
  /** 无效的 */
  UNVALID,
  /** 非安全的数 */
  UNSAFE,
  /** 大于最大值 */
  GREATER_THAN_MAX_VALUE,
  /** 小于最小值 */
  LESS_THAN_MIN_VALUE,
  /** 精度过大 */
  EXCESSIVE_PRECISION
}

/**
 * 数字字符串的校验结果
 */
export enum INTEGER_NUMBER_VALIDATOR {
  /** 有效的 */
  VALID,
  /** 无效的 */
  UNVALID,
  /** 非安全的数 */
  UNSAFE,
  /** 大于最大值 */
  GREATER_THAN_MAX_VALUE,
  /** 小于最小值 */
  LESS_THAN_MIN_VALUE,
  /** 是浮点数 */
  FLOAT
}

export interface CreateNumberToolReturn<R extends FLOAT_NUMBER_VALIDATOR | INTEGER_NUMBER_VALIDATOR> {
  validator: (value: number | string) => R
  formatter: {
    /**
     * 数字格式化函数
     * @param value 被格式化的值
     */
    (value: string | number): number | undefined
    /**
     * 数字格式化函数
     * @param value 被格式化的值
     * @param unvalidValue 无效数字时返回的值
     */
    (value: string | number, unvalidValue: number): number
  }
}

/**
 * 生成浮点数的校验/格式化函数
 * @param options 浮点数的校验/格式化配置对象
 */
function createFloatNumberTool(options: FloatNumberToolOptions): CreateNumberToolReturn<FLOAT_NUMBER_VALIDATOR> {
  const { min, max, precision, safe = true } = options

  const precisionRegExp =
    typeof precision === 'number' && precision > 0 ? new RegExp(`^-?\\d+\\.\\d{1,${precision}}$`) : null

  /**
   * 浮点数校验函数
   * @param value 被校验的值
   */
  function validator(value: string | number): FLOAT_NUMBER_VALIDATOR {
    value = String(value)

    if (!NumberRegExp.test(value)) return FLOAT_NUMBER_VALIDATOR.UNVALID

    const numberValue = parseFloat(value)
    if (min && numberValue < min) return FLOAT_NUMBER_VALIDATOR.LESS_THAN_MIN_VALUE
    if (max && numberValue > max) return FLOAT_NUMBER_VALIDATOR.GREATER_THAN_MAX_VALUE

    if (safe && !Number.isSafeInteger(parseInt(value))) return FLOAT_NUMBER_VALIDATOR.UNSAFE

    if (precisionRegExp && FloatNumberRegExp.test(value) && !precisionRegExp.test(value))
      return FLOAT_NUMBER_VALIDATOR.EXCESSIVE_PRECISION

    return FLOAT_NUMBER_VALIDATOR.VALID
  }

  /**
   * 浮点数格式化函数
   * @param value 被格式化的值
   * @param unvalidValue 无效的浮点数时返回的值
   */
  function formatter(value: string | number): number | undefined
  function formatter(value: string | number, unvalidValue: number): number
  function formatter(value: string | number, unvalidValue?: number) {
    value = String(value)

    switch (validator(value)) {
      case FLOAT_NUMBER_VALIDATOR.GREATER_THAN_MAX_VALUE:
        return max
      case FLOAT_NUMBER_VALIDATOR.LESS_THAN_MIN_VALUE:
        return min
      case FLOAT_NUMBER_VALIDATOR.VALID:
        return parseFloat(value)
      case FLOAT_NUMBER_VALIDATOR.EXCESSIVE_PRECISION: {
        const [integerString, decimalsString] = value.split('.')
        return parseFloat(`${integerString}.${decimalsString.slice(0, precision)}`)
      }
      default:
        return unvalidValue
    }
  }

  return { validator, formatter }
}

/**
 * 生成整数的校验/格式化函数
 * @param options 整数的校验/格式化配置对象
 */
function createIntegerNumberTool(options: IntegerNumberToolOptions): CreateNumberToolReturn<INTEGER_NUMBER_VALIDATOR> {
  const { min, max, safe = true } = options

  /**
   * 整数校验函数
   * @param value 被校验的值
   */
  function validator(value: string | number): INTEGER_NUMBER_VALIDATOR {
    value = String(value)

    if (!NumberRegExp.test(value)) return INTEGER_NUMBER_VALIDATOR.UNVALID
    if (FloatNumberRegExp.test(value)) return INTEGER_NUMBER_VALIDATOR.FLOAT

    const numberValue = parseInt(value)
    if (min && numberValue < min) return INTEGER_NUMBER_VALIDATOR.LESS_THAN_MIN_VALUE
    if (max && numberValue > max) return INTEGER_NUMBER_VALIDATOR.GREATER_THAN_MAX_VALUE

    if (safe && !Number.isSafeInteger(numberValue)) return INTEGER_NUMBER_VALIDATOR.UNSAFE

    return INTEGER_NUMBER_VALIDATOR.VALID
  }

  /**
   * 整数格式化函数
   * @param value 被格式化的值
   * @param unvalidValue 无效值时返回的值
   */
  function formatter(value: string | number): number | undefined
  function formatter(value: string | number, unvalidValue: number): number
  function formatter(value: string | number, unvalidValue?: number) {
    switch (validator(value)) {
      case INTEGER_NUMBER_VALIDATOR.GREATER_THAN_MAX_VALUE:
        return max
      case INTEGER_NUMBER_VALIDATOR.LESS_THAN_MIN_VALUE:
        return min
      case INTEGER_NUMBER_VALIDATOR.VALID:
      case INTEGER_NUMBER_VALIDATOR.FLOAT:
        return parseInt(value as string)
      default:
        return unvalidValue
    }
  }

  return { validator, formatter }
}

/**
 * 生成整数或浮点数的校验/格式化函数
 * @param options 数字的校验/格式化配置对象
 */
export function createNumberTool<O extends FloatNumberToolOptions | IntegerNumberToolOptions>(options: O) {
  return (
    options.mode === 'float' ? createFloatNumberTool(options) : createIntegerNumberTool(options)
  ) as O extends FloatNumberToolOptions
    ? CreateNumberToolReturn<FLOAT_NUMBER_VALIDATOR>
    : CreateNumberToolReturn<INTEGER_NUMBER_VALIDATOR>
}

/**
 * 浮点数校验结果的默认提示文字
 */
const defaultFloatNumberMessages: Record<FLOAT_NUMBER_VALIDATOR, string> = {
  [FLOAT_NUMBER_VALIDATOR.VALID]: '',
  [FLOAT_NUMBER_VALIDATOR.UNVALID]: '',
  [FLOAT_NUMBER_VALIDATOR.UNSAFE]: '',
  [FLOAT_NUMBER_VALIDATOR.GREATER_THAN_MAX_VALUE]: '',
  [FLOAT_NUMBER_VALIDATOR.LESS_THAN_MIN_VALUE]: '',
  [FLOAT_NUMBER_VALIDATOR.EXCESSIVE_PRECISION]: ''
}

/**
 * 整数校验结果的默认提示文字
 */
const defaultIntegerNumberMessages: Record<INTEGER_NUMBER_VALIDATOR, string> = {
  [INTEGER_NUMBER_VALIDATOR.VALID]: '',
  [INTEGER_NUMBER_VALIDATOR.UNVALID]: '',
  [INTEGER_NUMBER_VALIDATOR.UNSAFE]: '',
  [INTEGER_NUMBER_VALIDATOR.GREATER_THAN_MAX_VALUE]: '',
  [INTEGER_NUMBER_VALIDATOR.LESS_THAN_MIN_VALUE]: '',
  [INTEGER_NUMBER_VALIDATOR.FLOAT]: ''
}

/**
 * 创建一个将数字校验结果转换为提示信息的函数
 * @param options 数字校验配置对象
 * @param messages 数字校验结果的对应提示文字
 */
export function createNumberValidatorMessage<
  O extends FloatNumberToolOptions | IntegerNumberToolOptions,
  R extends FLOAT_NUMBER_VALIDATOR | INTEGER_NUMBER_VALIDATOR = O extends FloatNumberToolOptions
    ? FLOAT_NUMBER_VALIDATOR
    : INTEGER_NUMBER_VALIDATOR
>(options: O, messages: Partial<Record<R, string>>): (result: R) => undefined | string {
  const replaceRegExp = new RegExp(`{(${Object.keys(options).join('|')})}`, 'g')

  const finalMessages =
    options.mode === 'float'
      ? { ...defaultFloatNumberMessages, ...messages }
      : { ...defaultIntegerNumberMessages, ...messages }

  return function (result: R): string | undefined {
    return finalMessages[result]?.replace(replaceRegExp, (match, key) => {
      return (options[key as keyof O] ?? match) as string
    })
  }
}
